<?php

namespace app\test\controller;

use \Alipay\aop\AopClient;
use \Alipay\aop\request\AlipayAppTokenGetRequest;
use Alipay\aop\request\AlipaySystemOauthTokenRequest;
use Alipay\aop\request\AlipayUserInfoShareRequest;
use app\common\lib\mini\alipay\AmpHelper;
use Exception;
use think\Cache;
use think\Controller;
use app\test\controller\WXBizDataCrypt;

/**
 *
 * Class TestMiniProgrom
 * @package app\api\controller\v8
 */
class TestMiniProgrom extends Controller
{

    const LOG_PRE = '小程序bug：';

    private $wechatAppid = 'wxa417c0a7a1e75643';
    private $wechatAppSecret = '3de639e224e0a338dde771855583b494';

    private $qqAppid = '1110071592';
    private $qqAppSecret = 'CafZm4MPOVNrlJ0t';

    private $swanAppKey = 'bG02CRHGTq0THMCP9tntpKZAuEwDzac0';
    private $swanAppSecret = '1BtnlSuZKanmjQZLcBEOXLHoNo2caLPW';

    private $ttAppid = 'tta48ad2373ba085a1';
    private $ttAppSecret = '062db06ebf49b6388d6a6f2383259c4dbb48c7b7';

    const SWAN_ACCESS_TOKEN_KEY = 'swan_access_token';

    public function get_alipay()
    {
        $code = input('code/s');//todo 校验参数
//此方法总是报 aop.invalid-auth-token 错误，所以这里只使用获取token的方法
        $originUserData = AmpHelper::getAmpUserInfoByAuthCode($code);

//                $originUserData = AmpHelper::getAmpToken($code);
//access_token = "authbseB6bc2236585ac49dba4b61dc7a0d87X00"
//alipay_user_id = "20881041370363830885314300018300"
//expires_in = {int} 31536000
//re_expires_in = {int} 31536000
//refresh_token = "authbseB3af77057ae5c48e6a95153bd9ed30F00"
//user_id = "2088632992926000"
//        $aop = new AopClient ();
//        $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
//        $aop->appId = config('mini')['ALIPAY']['app_id'];
//        $aop->rsaPrivateKey = config('mini')['ALIPAY']['private_key'];
//        $aop->alipayrsaPublicKey = config('mini')['ALIPAY']['publick_key'];
//        $aop->apiVersion = '1.0';
//        $aop->signType = 'RSA2';
//        $aop->postCharset = 'UTF-8';
//        $aop->format = 'json';
//        $request = new AlipayUserInfoShareRequest ();
//        $result = $aop->execute($request, $originUserData['access_token']);
//
//        $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
//        $resultCode = $result->$responseNode->code;
//        if (!empty($resultCode) && $resultCode == 10000)
//        {
//            echo "成功";
//        } else
//        {
//            echo "失败";
//        }
        return show(config('code.success'), 'ok', $originUserData);

    }


    private function index()
    {

        $encrypted_data = input('encrypted_data/s');
        $iv = input('iv/s');
        $code = input('code/s');
        // todo 校验参数  SWAN：百度小程序；WEAPP：微信小程序；QQ:qq小程序；ALIPAY：支付宝小程序；TT：字节跳动小程序；H5：h5；
        $env_type = input('env_type/s');

        //TT：字节跳动小程序；
        $rawData = input('rawData/s');
        $signature = input('signature/s');
        //TT：字节跳动小程序；

        $session_key = $this->getSessionKey($code, $env_type);
        switch ($env_type)
        {
            case 'TT':
                if ($session_key)
                {
                    $data = $this->ttDecrypt($encrypted_data, $iv, $session_key, $signature, $rawData);
                    if ($data)
                    {
                        //todo 入库
                        return show(config('code.success'), 'ok', $data);
                    }
                }
                return show(config('code.error'), 'failed');
                break;
            case 'SWAN':
                //百度小程序，解密数据，获取用户信息（注意：不包含unionid）
                $data = $this->swanDecrypt($encrypted_data, $iv, $this->swanAppKey, $session_key);
                if ($data)
                {
                    //获取unionid
                    //先查询redis缓存中是否存在access_token，如果存在那么使用，如果不存在，那么获取并放到redis缓存中
                    $access_token = Cache::get(self::SWAN_ACCESS_TOKEN_KEY);
                    if (empty($access_token) || $access_token === '')
                    {
                        //获取access_token（readme：https://smartprogram.baidu.com/docs/develop/serverapi/power_exp/）
                        $access_token_url = 'https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=' . $this->swanAppKey . '&client_secret=' . $this->swanAppSecret . '&scope=smartapp_snsapi_base';
                        $result_of_get_access_token = json_decode(file_get_contents($access_token_url), false);
                        if (isset($result_of_get_access_token->errcode))
                        {
                            Cache::rm(self::SWAN_ACCESS_TOKEN_KEY);
                        } else
                        {
                            $access_token = $result_of_get_access_token->access_token;
                            Cache::set(self::SWAN_ACCESS_TOKEN_KEY, $access_token, $result_of_get_access_token->expires_in);
                        }
                    }

                    //获取unionid（readme：https://smartprogram.baidu.com/docs/develop/api/open/log_getunionid/）
                    $union_url = 'https://openapi.baidu.com/rest/2.0/smartapp/getunionid?access_token=' . $access_token;
                    $body = ['openid' => $data_decrypt['openid']];
                    $header = ['Content-Type' => 'application/x-www-form-urlencoded'];
                    $result_of_get_unionid = curl_post($union_url, $body, $header);
                    $result_of_get_unionid = json_decode($result_of_get_unionid, true);
                    if (isset($result_of_get_unionid['data'], $result_of_get_unionid['data']['unionid']))
                    {
                        //todo 获取了uinonid
                        $data_decrypt['unionid'] = $result_of_get_unionid['data']['unionid'];
                        //todo 入库操作，返回用户信息（包含unionid）
                        return show(config('code.success'), 'ok', $data_decrypt);
                    } else
                    {
                        Cache::rm(self::SWAN_ACCESS_TOKEN_KEY);//如果错误，那么删除access_token，因为有可能是access_token错了
                    }
                } else
                {
                    return show(config('code.error'), 'failed');
                }
                break;
            case 'WEAPP':
            case 'QQ':
                if ('WEAPP' === $env_type)
                {
                    $appId = $this->wechatAppid;
                } else
                {
                    $appId = $this->qqAppid;
                }
                $pc = new WXBizDataCrypt($appId, $session_key);
                $errCode = $pc->decryptData($encrypted_data, $iv, $data);
                if (ErrorCode::$OK === $errCode)
                {
                    $result = json_decode($data, false);
                    //todo 入库操作，返回用户信息
                    return show(config('code.success'), 'ok', $result);
                } else
                {
                    return show(config('code.error'), 'failed');
                }
                break;
        }

//swan返回示例：
//headimgurl = "https://himg.bdimg.com/sys/portrait/item/be756d61726b6c69626e8042"
//nickname = "百度网友ed3e9fc"
//openid = "qsN6gzg9LLCWzbmoQIDh-7NovZ"
//sex = {int} 0

//qq/wechat返回示例：
//avatarUrl = "https://thirdqq.qlogo.cn/qqapp/1110071592/6BB92EC599C6982D7C2F19E5F8A089C7/100"
//city = "南京"
//country = "中国"
//gender = {int} 1
//language = "zh_CN"
//nickName = "o︻$▅▆▇◤"
//openId = "6BB92EC599C6982D7C2F19E5F8A089C7"
//province = "江苏"
//unionId = ""
//watermark = {stdClass} [2]
// appid = "1110071592"
// timestamp = {int} 1575271115

    }

    /**
     * 日历词条接口
     * @return Json
     */
    private
    function calendar_test()
    {
        date("l"); //data就可以获取英文的星期比如Sunday
        date("w"); //这个可以获取数字星期比如123，注意0是星期日

        $data = [
            "text" => "鸡汤词条",
            "month" => 11,
            "day" => 8,
            "week_num" => 4,
            "week" => "星期四",
            "astro_text" => [
                0 => [
                    "time" => "01:08",
                    "text" => "天象词条1"
                ], 1 => [
                    "time" => "05:28",
                    "text" => "天象词条2"
                ], 2 => [
                    "time" => "11:36",
                    "text" => "天象词条3"
                ]
            ]
        ];
        return show(config('code.success'), '获取成功', $data);
    }

    /**
     * 日历签到接口
     * @return Json
     */
    private
    function calendar_attendance()
    {
        $uid = input('param.mini_uid');
        $data = [
            'date' => 'Jan 29 Mon',
            'day' => 29,
            'suitable_avoid_text' => '宜励志',
            "astro_text" => [
                0 => [
                    "time" => "01:08",
                    "text" => "天象词条1"
                ], 1 => [
                    "time" => "05:28",
                    "text" => "天象词条2"
                ], 2 => [
                    "time" => "11:36",
                    "text" => "天象词条3"
                ]
            ],
            'text' => '被人揭下面具是有一种失败，
自己揭下面具却是一种胜利。',
            'sign_in_time' => [1, 1, 1, 1, 1, 1, 0],
        ];
        return show(config('code.success'), '获取成功', $data);
    }

    /**
     * 检验数据的真实性，并且获取解密后的明文.
     * @param $appId
     * @param $sessionKey
     * @param $encryptedData string 加密的用户数据
     * @param $iv string 与用户数据一同返回的初始向量
     * @return int 成功0，失败返回对应的错误码
     */
    private function wechatOrQQDecrypt($appId, $sessionKey, $encryptedData, $iv): int
    {
        if (strlen($sessionKey) !== 24)
        {
            return false;
        }
        $aesKey = base64_decode($sessionKey);


        if (strlen($iv) !== 24)
        {
            return false;
        }
        $aesIV = base64_decode($iv);

        $aesCipher = base64_decode($encryptedData);

        $result = openssl_decrypt($aesCipher, 'AES-128-CBC', $aesKey, 1, $aesIV);

        $dataObj = json_decode($result, true);
        if ($dataObj === NULL)
        {
            return false;
        }
        if ($dataObj->watermark->appid !== $appId)
        {
            return false;
        }
        return $result;
    }

    /**
     * 百度小程序 数据解密：低版本使用mcrypt库（PHP < 5.3.0），高版本使用openssl库（PHP >= 5.3.0）。
     *
     * @param string $ciphertext 待解密数据，返回的内容中的data字段
     * @param string $iv 加密向量，返回的内容中的iv字段
     * @param string $app_key 创建小程序时生成的app_key
     * @param string $session_key 登录的code换得的
     * @return string | false
     */
    private function swanDecrypt($ciphertext, $iv, $app_key, $session_key)
    {
        $session_key = base64_decode($session_key);
        $iv = base64_decode($iv);
        $ciphertext = base64_decode($ciphertext);

        if (function_exists('openssl_decrypt'))
        {
            $plaintext = openssl_decrypt($ciphertext, 'AES-192-CBC', $session_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
        } else
        {
            $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, null, MCRYPT_MODE_CBC, null);
            mcrypt_generic_init($td, $session_key, $iv);
            $plaintext = mdecrypt_generic($td, $ciphertext);
            mcrypt_generic_deinit($td);
            mcrypt_module_close($td);
        }
        if ($plaintext === false)
        {
            return false;
        }

        // trim pkcs#7 padding
        $pad = ord(substr($plaintext, -1));
        $pad = ($pad < 1 || $pad > 32) ? 0 : $pad;
        $plaintext = substr($plaintext, 0, strlen($plaintext) - $pad);

        // trim header
        $plaintext = substr($plaintext, 16);
        // get content length
        $unpack = unpack('Nlen/', substr($plaintext, 0, 4));
        // get content
        $content = substr($plaintext, 4, $unpack['len']);
        // get app_key
        $app_key_decode = substr($plaintext, $unpack['len'] + 4);

        return $app_key === $app_key_decode ? json_decode($content, true) : false;
    }


    /**
     * 今日头条小程序 验数据是否合法，并且解密数据
     * @param $ciphertext
     * @param $iv
     * @param $session_key
     * @param $signature
     * @param $rawData
     * @return bool|mixed
     */
    private
    function ttDecrypt($ciphertext, $iv, $session_key, $signature, $rawData)
    {
        //先校验数据是否合法
        if ($signature !== sha1($rawData . $session_key))
        {
            return false;
        } else
        {

            $session_key = base64_decode($session_key);
            $iv = base64_decode($iv);
            $ciphertext = base64_decode($ciphertext);

            if (function_exists('openssl_decrypt'))
            {
                $plaintext = openssl_decrypt($ciphertext, 'AES-128-CBC', $session_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
            } else
            {
                $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, null, MCRYPT_MODE_CBC, null);
                mcrypt_generic_init($td, $session_key, $iv);
                $plaintext = mdecrypt_generic($td, $ciphertext);
                mcrypt_generic_deinit($td);
                mcrypt_module_close($td);
            }
            if ($plaintext === false)
            {
                return false;
            } else
            {
                return json_decode(trim($plaintext), true);;
            }
        }
    }


    /**
     * 获取session key
     * @param $code
     * @param $env_type SWAN：百度小程序；WEAPP：微信小程序；QQ:qq小程序；TT：字节跳动小程序；
     * @return bool
     */
    private function getSessionKey($code, $env_type): bool
    {
        try
        {
            switch ($env_type)
            {
                case 'SWAN':
                    $access_token_url = 'https://spapi.baidu.com/oauth/jscode2sessionkey?client_id=' . $this->swanAppKey . '&sk=' . $this->swanAppSecret . '&code=' . $code;
                    break;
                case 'WEAPP':
                    $access_token_url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' . $this->wechatAppid . '&secret=' . $this->wechatAppSecret . '&js_code=' . $code . '&grant_type=authorization_code';
                    break;
                case 'QQ':
                    $access_token_url = 'https://api.q.qq.com/sns/jscode2session?appid=' . $this->qqAppid . '&secret=' . $this->qqAppSecret . '&js_code=' . $code . '&grant_type=authorization_code';
                    break;
                case 'TT':
                    $access_token_url = 'https://developer.toutiao.com/api/apps/jscode2session?appid=' . $this->ttAppid . '&secret=' . $this->ttAppSecret . '&code=' . $code;
                    break;
            }

            $result = file_get_contents($access_token_url);
            $result_arr = json_decode($result, false);
            if (isset($result_arr->session_key))
            {
                return $result_arr->session_key;
            }
        }
        catch (Exception $e)
        {
            Log::write(self::LOG_PRE . $e->getMessage() . PHP_EOL, 'error', true);
        }

        return false;
    }
}
